package de.dailab.graphworld.bean;

import de.dailab.graphworld.bean.memory.GoldTaken;
import de.dailab.graphworld.bean.memory.NodeInfo;
import de.dailab.graphworld.ontology.Edge;
import de.dailab.graphworld.ontology.GraphWorldException;
import de.dailab.graphworld.ontology.Node;
import de.dailab.graphworld.pathfinding.AStar;
import de.dailab.graphworld.world.ExtendedGraphWorldAgentBean;
import de.dailab.jiactng.agentcore.action.Action;
import de.dailab.jiactng.agentcore.action.ActionResult;
import de.dailab.jiactng.agentcore.comm.IGroupAddress;
import de.dailab.jiactng.agentcore.comm.message.JiacMessage;
import de.dailab.jiactng.agentcore.ontology.IActionDescription;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;

/**
 *
 * @author Kseniya
 */
public class RandomMover extends BeanPhysical {

    private IActionDescription actionExplore;
    private IActionDescription actionMove;
    private IActionDescription actionTakeGold;
    private IActionDescription actionDropGold;
    private final Queue<Node> moves = new LinkedList<>();
    private Integer maxCapacity;
    private int goldAmountCarrying = 0;

    @Override
    public void doStart() {
        super.doStart();
        actionExplore = thisAgent.searchAction(new Action(ExtendedGraphWorldAgentBean.ACTION_EXPLORE));
        actionMove = thisAgent.searchAction(new Action(ExtendedGraphWorldAgentBean.ACTION_MOVE));
        actionTakeGold = thisAgent.searchAction(new Action(ExtendedGraphWorldAgentBean.ACTION_TAKE_GOLD));
        actionDropGold = thisAgent.searchAction(new Action(ExtendedGraphWorldAgentBean.ACTION_DROP_GOLD));
        memory.write(new GoldTaken(0));
    }

    @Override
    public void execute() {
        super.execute();
        if (!isInitialisedInWorld()) {
            return;
        }
        // do the next move if defined or explore to get the next move
        if (!moves.isEmpty()) {
            move(moves.peek());
        } else {
            explore();
        }
    }

    @Override
    public void receiveResult(ActionResult result) {
        String actionName = result.getAction().getName();
        if (result.getFailure() != null) {
            if (ExtendedGraphWorldAgentBean.ACTION_MOVE.equals(actionName)) {
                if (result.getFailure() instanceof GraphWorldException) {
                    GraphWorldException failure = (GraphWorldException) result.getFailure();
                    if (failure.getMessage().startsWith("Target position is not a neighbour")) {
                        moves.clear();
                        explore();
                    }
                }
            } else {
                log.error(actionName);
                log.error(result.getFailure());
                log.error("stored current position: " + memory.read(new NodeInfo(null)).getNode().getID());
            }
        } else {
            switch (actionName) {
                case ExtendedGraphWorldAgentBean.ACTION_INIT_TEAM_POSITION:
                    Node curr = (Node) result.getResults()[0];
                    memory.write(new NodeInfo(curr));
                    explore();
                    break;
                case ExtendedGraphWorldAgentBean.ACTION_EXPLORE:
                    Set<Node> neighbours = new HashSet<>((Set<Node>) result.getResults()[0]);
                    consumeExplore(neighbours);
                    break;
                case ExtendedGraphWorldAgentBean.ACTION_MOVE:
                    Node movedTo = (Node) result.getResults()[0];
//                    // for testing
//                    ArrayList<Edge> findPath = AStar.findPath(movedTo, 0);
//                    System.out.println("found path from " + movedTo.getID() + " to home: " + findPath);
                    moves.poll();
                    memory.update(new NodeInfo(null), new NodeInfo(movedTo));
                    if (movedTo.getGold() > 0 && goldAmountCarrying < maxCapacity) {
                        takeGold();
                        memory.update(new GoldTaken(null), new GoldTaken(movedTo.getGold().intValue()));
                    } else if (goldAmountCarrying > 0 && movedTo.getID() == 0) {
                        dropGold();
                    }
                    break;
                case ExtendedGraphWorldAgentBean.ACTION_TAKE_GOLD:
                    Integer wasGold = memory.read(new GoldTaken(null)).getAmount();
//                    System.out.println("was gold: " + wasGold);
                    goldAmountCarrying = Math.min(goldAmountCarrying + wasGold, maxCapacity);
                    // go home
                    moves.clear();
                    planPathToHome();
                    break;
                case ExtendedGraphWorldAgentBean.ACTION_DROP_GOLD:
                    goldAmountCarrying = 0;
                    break;
                default:
                    break;
            }
        }
        super.receiveResult(result);
    }

    private void explore() {
        this.invoke(actionExplore, null, this);
    }

    private void queueNextMove(Node node) {
        moves.offer(node);
    }

    private void move(Node target) {
        this.invoke(actionMove, new Serializable[]{target}, this);
    }

    private void takeGold() {
        this.invoke(actionTakeGold, new Serializable[]{maxCapacity - goldAmountCarrying}, this);
    }

    private void dropGold() {
        this.invoke(actionDropGold, null, this);
    }

    private void planPathToHome() {
        Node currPos = memory.read(new NodeInfo(null)).getNode();

        List<Edge> path;

        if (currPos.getID() == 0) {
            path = Collections.emptyList();
        } else {
            path = AStar.findPath(currPos, 0);
        }
        for (Edge edge : path) {
            moves.add(edge.getTarget());
        }
    }

    /**
     * should be called after successful explore only. Updates memory, selects
     * next node to move to and queues an appropriate move action.
     *
     * @param explored set of explored nodes
     */
    private void consumeExplore(Set<Node> explored) {
        // select next move randomly
        Node selected = selectMoveTarget(explored);
        // queue next move
        queueNextMove(selected);
    }

    private Node selectMoveTarget(Collection<Node> nodes) {
        Node current = memory.read(new NodeInfo(null)).getNode();
        Iterator<Node> it = nodes.iterator();
        while (it.hasNext()) {
            Node node = it.next();
            if (node.getID() == current.getID()) {
                it.remove();
            }
        }
        return nodes.iterator().next();
    }

    @Override
    protected boolean isInitialisedInWorld() {
        NodeInfo storedPosition = memory.read(new NodeInfo(null));
        return storedPosition != null;
    }

    @Override
    protected Collection<IGroupAddress> getGroups() {
        return Collections.emptyList();
    }

    @Override
    public void consume(JiacMessage msg) {
    }

    /**
     * Get the value of maxCapacity
     *
     * @return the value of maxCapacity
     */
    public Integer getMaxCapacity() {
        return maxCapacity;
    }

    /**
     * Set the value of maxCapacity
     *
     * @param maxCapacity new value of maxCapacity
     */
    public void setMaxCapacity(Integer maxCapacity) {
        this.maxCapacity = maxCapacity;
    }
}
